
/******************************************************************************
 * MODULE     : typeset.cpp
 * DESCRIPTION: typeset the tree being edited
 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "edit_typeset.hpp"
#include "Bridge/impl_typesetter.hpp"
#include "analyze.hpp"
#include "convert.hpp"
#include "converter.hpp"
#include "file.hpp"
#include "iterator.hpp"
#include "merge_sort.hpp"
#include "new_style.hpp"
#include "observers.hpp"
#include "tm_buffer.hpp"
#include "tm_timer.hpp"
#include "tree_modify.hpp"
#include "tree_observer.hpp"
#include <climits>

using namespace moebius;

// box empty_box (path ip, int x1=0, int y1=0, int x2=0, int y2=0);
bool enable_fastenv= false;

/******************************************************************************
 * Contructors, destructors and notification of modifications
 ******************************************************************************/

edit_typeset_rep::edit_typeset_rep ()
    : editor_rep (), // NOTE: ignored by the compiler, but suppresses warning
      the_style (TUPLE), cur (hashmap<string, tree> (UNINIT)), stydef (UNINIT),
      pre (UNINIT), init (UNINIT), fin (UNINIT), grefs (UNINIT),
      env (drd, buf->buf->master, buf->data->ref,
           (buf->prj == NULL ? grefs : buf->prj->data->ref), buf->data->aux,
           (buf->prj == NULL ? buf->data->aux : buf->prj->data->aux),
           buf->data->att,
           (buf->prj == NULL ? buf->data->att : buf->prj->data->att)),
      ttt (new_typesetter (env, subtree (et, rp), reverse (rp))) {
  init_update ();
}

edit_typeset_rep::~edit_typeset_rep () { delete_typesetter (ttt); }

void
edit_typeset_rep::set_data (new_data data) {
  set_style (data->style);
  set_init (data->init);
  set_fin (data->fin);
  set_ref (data->ref);
  set_aux (data->aux);
  set_att (data->att);
  notify_page_change ();
  add_init (data->init);
  notify_change (THE_DECORATIONS);
  typeset_invalidate_env ();
  iterator<string> it= iterate (data->att);
  while (it->busy ()) {
    string key= it->next ();
    (void) call (string ("notify-set-attachment"), buf->buf->name, key,
                 data->att[key]);
  }
}

void
edit_typeset_rep::get_data (new_data& data) {
  data->style= get_style ();
  data->init = get_init ();
  data->fin  = get_fin ();
  data->ref  = get_ref ();
  data->aux  = get_aux ();
  data->att  = get_att ();
}

typesetter
edit_typeset_rep::get_typesetter () {
  return ttt;
}
tree
edit_typeset_rep::get_style () {
  return the_style;
}
void
edit_typeset_rep::set_style (tree t) {
  the_style= copy (t);
}
hashmap<string, tree>
edit_typeset_rep::get_init () {
  return init;
}
hashmap<string, tree>
edit_typeset_rep::get_fin () {
  return fin;
}
hashmap<string, tree>
edit_typeset_rep::get_ref () {
  return buf->data->ref;
}
hashmap<string, tree>
edit_typeset_rep::get_aux () {
  return buf->data->aux;
}
hashmap<string, tree>
edit_typeset_rep::get_att () {
  return buf->data->att;
}
void
edit_typeset_rep::set_fin (hashmap<string, tree> H) {
  fin= H;
}
void
edit_typeset_rep::set_ref (hashmap<string, tree> H) {
  buf->data->ref= H;
}
void
edit_typeset_rep::set_aux (hashmap<string, tree> H) {
  buf->data->aux= H;
}
void
edit_typeset_rep::set_att (hashmap<string, tree> H) {
  buf->data->att= H;
}

tree
edit_typeset_rep::get_ref (string key) {
  return buf->data->ref[key];
}

void
edit_typeset_rep::set_ref (string key, tree im) {
  buf->data->ref (key)= im;
}

void
edit_typeset_rep::reset_ref (string key) {
  buf->data->ref->reset (key);
}

static string
concat_as_string (tree t) {
  if (is_atomic (t)) return t->label;
  else if (is_func (t, CONCAT)) {
    string r;
    for (int i= 0; i < N (t); i++)
      r << concat_as_string (t[i]);
    return r;
  }
  else return "?";
}

array<string>
edit_typeset_rep::find_refs (string val, bool global) {
  tree a= as_tree (buf->data->ref);
  if (global && buf->prj != NULL) a= as_tree (buf->prj->data->ref);
  array<string> v;
  int           i, n= N (a);
  for (i= 0; i < n; i++)
    if (N (a[i]) >= 2 && N (a[i][1]) >= 1 &&
        concat_as_string (a[i][1][0]) == val)
      v << a[i][0]->label;
  return v;
}

array<string>
edit_typeset_rep::list_refs (bool global) {
  tree a= as_tree (buf->data->ref);
  if (global && buf->prj != NULL) a= as_tree (buf->prj->data->ref);
  array<string> v;
  int           i, n= N (a);
  for (i= 0; i < n; i++)
    v << a[i][0]->label;
  merge_sort (v);
  return v;
}

tree
edit_typeset_rep::get_aux (string key) {
  return buf->data->aux[key];
}

void
edit_typeset_rep::set_aux (string key, tree im) {
  buf->data->aux (key)= im;
}

void
edit_typeset_rep::reset_aux (string key) {
  buf->data->aux->reset (key);
}

array<string>
edit_typeset_rep::list_auxs (bool global) {
  tree a= as_tree (buf->data->aux);
  if (global && buf->prj != NULL) a= as_tree (buf->prj->data->aux);
  array<string> v;
  int           i, n= N (a);
  for (i= 0; i < n; i++)
    v << a[i][0]->label;
  merge_sort (v);
  return v;
}

tree
edit_typeset_rep::get_att (string key) {
  return buf->data->att[key];
}

void
edit_typeset_rep::set_att (string key, tree im) {
  buf->data->att (key)= im;
}

void
edit_typeset_rep::reset_att (string key) {
  buf->data->att->reset (key);
}

array<string>
edit_typeset_rep::list_atts (bool global) {
  tree a= as_tree (buf->data->att);
  if (global && buf->prj != NULL) a= as_tree (buf->prj->data->att);
  array<string> v;
  int           i, n= N (a);
  for (i= 0; i < n; i++)
    v << a[i][0]->label;
  merge_sort (v);
  return v;
}

void
edit_typeset_rep::set_init (hashmap<string, tree> H) {
  init= hashmap<string, tree> (UNINIT);
  add_init (H);
}

void
edit_typeset_rep::add_init (hashmap<string, tree> H) {
  init->join (H);
  ::notify_assign (ttt, path (), subtree (et, rp));
  notify_change (THE_ENVIRONMENT);
}

void
edit_typeset_rep::clear_local_info () {
  buf->data->ref= hashmap<string, tree> ();
  buf->data->aux= hashmap<string, tree> ();
}

/******************************************************************************
 * Miscellaneous routines for lengths arithmetic (should be elsewhere)
 ******************************************************************************/

SI
edit_typeset_rep::as_length (string l) {
  return env->as_length (l);
}

string
edit_typeset_rep::add_lengths (string l1, string l2) {
  return env->add_lengths (l1, l2);
}

string
edit_typeset_rep::sub_lengths (string l1, string l2) {
  return env->sub_lengths (l1, l2);
}

string
edit_typeset_rep::max_lengths (string l1, string l2) {
  return env->max_lengths (l1, l2);
}

string
edit_typeset_rep::min_lengths (string l1, string l2) {
  return env->min_lengths (l1, l2);
}

string
edit_typeset_rep::multiply_length (double x, string l) {
  return env->multiply_length (x, l);
}

bool
edit_typeset_rep::is_length (string s) {
  return env->is_length (s);
}

double
edit_typeset_rep::divide_lengths (string l1, string l2) {
  return env->divide_lengths (l1, l2);
}

/******************************************************************************
 * Processing preamble
 ******************************************************************************/

void
use_modules (tree t) {
  if (is_tuple (t))
    for (int i= 0; i < N (t); i++) {
      string s= as_string (t[i]);
      if (starts (s, "(")) eval ("(use-modules " * s * ")");
      else if (s != "") eval ("(plugin-initialize '" * s * ")");
    }
}

void
edit_typeset_rep::typeset_style_use_cache (tree styles_orig) {
  tree styles= preprocess_style (styles_orig, buf->buf->master);
  // cout << "Typesetting style using cache " << style << LF;
  bool                  ok;
  hashmap<string, tree> H;
  tree                  t;
  style_get_cache (styles, H, t, ok);
  if (ok) {
    env->patch_env (H);
    ok= drd->set_locals (t);
    drd->set_environment (H);
  }
  if (!ok) {
    // cout << "Typeset without cache " << style << LF;
    if (!is_tuple (styles)) TM_FAILED ("tuple expected as style");
    H  = get_style_env (styles);
    drd= get_style_drd (styles);
    style_set_cache (styles, H, drd->get_locals ());
    env->patch_env (H);
    drd->set_environment (H);
  }
  use_modules (env->read (THE_MODULES));
}

void
edit_typeset_rep::typeset_preamble () {
  env->write_default_env ();
  typeset_style_use_cache (the_style);
  env->update ();
  env->read_env (stydef);
  env->patch_env (init);
  env->update ();
  env->read_env (pre);
  drd->heuristic_init (pre);
}

void
edit_typeset_rep::typeset_prepare () {
  env->base_file_name= buf->buf->master;
  env->read_only     = buf->buf->read_only;
  env->write_default_env ();
  env->patch_env (pre);
  env->style_init_env ();
  env->update ();
}

void
edit_typeset_rep::init_update () {
  if (buf->prj != NULL) {
    string id = as_string (delta (buf->prj->buf->name, buf->buf->name));
    string lab= "part:" * id;
    hashmap<string, tree> aux= env->global_aux;
    hashmap<string, tree> ref= env->global_ref;
    if (aux->contains ("parts")) {
      tree parts= aux["parts"];
      if (is_func (parts, DOCUMENT))
        for (int i= 0; i < N (parts); i++)
          if (is_tuple (parts[i]) && N (parts[i]) >= 1)
            if (parts[i][0] == id)
              for (int j= 1; j + 1 < N (parts[i]); j+= 2)
                if (is_atomic (parts[i][j])) {
                  buf->data->init (parts[i][j]->label)= copy (parts[i][j + 1]);
                  init (parts[i][j]->label)           = copy (parts[i][j + 1]);
                }
    }
    if (ref->contains (lab)) {
      tree val= ref[lab];
      if (is_tuple (val) && N (val) >= 2 && val[1] != tree (UNINIT)) {
        buf->data->init (PAGE_FIRST)= copy (val[1]);
        init (PAGE_FIRST)           = copy (val[1]);
      }
    }
  }
  else if (buf->data->init["part-flag"] == "true") grefs= copy (buf->data->ref);
}

void
edit_typeset_rep::drd_update () {
  typeset_exec_until (tp);
  drd->heuristic_init (cur[tp]);
}

/******************************************************************************
 * Routines for getting information
 ******************************************************************************/

void
edit_typeset_rep::typeset_invalidate_env () {
  cur= hashmap<path, hashmap<string, tree>> (hashmap<string, tree> (UNINIT));
}

static void
restricted_exec (edit_env env, tree t, int end) {
  if (is_func (t, ASSIGN, 2) && end == 2) env->exec (t);
  else if (is_document (t) || is_concat (t))
    for (int i= 0; i < min (end, 10); i++)
      restricted_exec (env, t[i], arity (t[i]));
  else if (is_compound (t, "hide-preamble", 1) ||
           is_compound (t, "show-preamble", 1))
    env->exec (t[0]);
  else if (is_compound (t, "script-input", 4) && end == 2) {
    if (env->read (MODE) == "text") {
      env->write (MODE, "prog");
      env->write (PROG_LANGUAGE, t[0]);
    }
  }
}

static tree
filter_format (tree fm, int i, int n) {
  array<tree> r;
  for (int k= 0; k < N (fm); k++)
    if (is_func (fm[k], CWITH) && N (fm[k]) >= 4 && is_int (fm[k][0]) &&
        is_int (fm[k][1])) {
      int j1= as_int (fm[k][0]->label);
      int j2= as_int (fm[k][1]->label);
      if (j1 > 0) j1--;
      else j1+= n;
      if (j2 > 0) j2--;
      else j2+= n;
      if (i >= j1 && i <= j2) r << fm[k](2, N (fm[k]));
    }
  return tree (TFORMAT, r);
}

static void
table_descend (tree& t, path& p, tree& fm) {
  while (!is_nil (p)) {
    if (L (t) == TFORMAT && p->item == N (t) - 1) {
      array<tree> r;
      for (int k= 0; k < N (t) - 1; k++)
        if (is_func (t[k], CWITH, 6) && is_atomic (t[k][4]) &&
            !starts (t[k][4]->label, "cell-"))
          r << t[k];
      fm= tree (TFORMAT, r);
      t = t[N (t) - 1];
      p = p->next;
    }
    else if ((L (t) == TABLE || L (t) == ROW) && p->item >= 0 &&
             p->item < N (t)) {
      fm= filter_format (fm, p->item, N (t));
      t = t[p->item];
      p = p->next;
    }
    else break;
  }
}

static void
define_style_macros (edit_env& env, tree t) {
  if (is_document (t) || is_concat (t)) {
    int i, n= N (t);
    for (i= 0; i < n; i++)
      define_style_macros (env, t[i]);
  }
  else if (is_func (t, ASSIGN, 2) && is_atomic (t[0]))
    env->write (t[0]->label, t[1]);
}

void
edit_typeset_rep::typeset_exec_until (path p) {
  // FIXME: we should ensure that p is inside the document
  // if (!(rp <= p)) p= correct_cursor (et, rp * 0);

  // time_t t1= texmacs_time ();
  if (has_changed (THE_TREE + THE_ENVIRONMENT))
    if (p != correct_cursor (et, rp * 0)) {
      if (DEBUG_STD) std_warning << "resynchronizing for path " << p << "\n";
      // apply_changes ();
    }
  if (p == tp && inside_graphics (true) && p != closest_inside (et, p)) {
    // cout << "TeXmacs] Warning: corrected cursor\n";
    tp= closest_inside (et, tp);
    p = tp;
  }

  // cout << "Exec until " << p << LF;
  if (N (cur[p]) != 0) return;
  if (N (cur) >= 25) // avoids out of memory in weird cases
    typeset_invalidate_env ();
  typeset_prepare ();
  if (enable_fastenv) {
    if (!(rp < p)) {
      failed_error << "Erroneous path " << p << "\n";
      TM_FAILED ("invalid typesetting path");
    }
    tree t= subtree (et, rp);
    path q= path_up (p / rp);
    while (!is_nil (q)) {
      int i= q->item;
      restricted_exec (env, t, i);
      if (L (t) == TFORMAT && i == N (t) - 1) {
        tree fm= tree (TFORMAT);
        table_descend (t, q, fm);
        if (!is_nil (q))
          for (int k= 0; k < N (fm); k++)
            if (is_func (fm[k], CWITH, 2))
              env->write (fm[k][0]->label, fm[k][1]);
      }
      else {
        tree w= drd->get_env_child (t, i, tree (ATTR));
        if (w == "") break;
        // cout << "t= " << t << "\n";
        // cout << "i= " << i << "\n";
        // cout << "w= " << w << "\n";
        tree ww (w, N (w));
        for (int j= 0; j < N (w); j+= 2) {
          // cout << w[j] << " := " << env->exec (w[j+1]) << "\n";
          ww[j + 1]= env->exec (w[j + 1]);
        }
        for (int j= 0; j < N (w); j+= 2)
          env->write (w[j]->label, ww[j + 1]);
        t= t[i];
        q= q->next;
      }
    }
    if (env->read (PREAMBLE) == "true") env->write (MODE, "src");
  }
  else exec_until (ttt, p / rp);
  if (env->read (MODE) == "src" && env->read (PREAMBLE) != "true")
    define_style_macros (env, subtree (et, rp));
  env->read_env (cur (p));
  // time_t t2= texmacs_time ();
  // if (t2 - t1 >= 10) cout << "typeset_exec_until took " << t2-t1 << "ms\n";
}

tree
edit_typeset_rep::get_full_env () {
  typeset_exec_until (tp);
  return as_tree (cur[tp]);
}

bool
edit_typeset_rep::defined_at_cursor (string var) {
  typeset_exec_until (tp);
  return cur[tp]->contains (var);
}

tree
edit_typeset_rep::get_env_value (string var, path p) {
  typeset_exec_until (p);
  tree t= cur[p][var];
  return is_func (t, BACKUP, 2) ? t[0] : t;
}

tree
edit_typeset_rep::get_env_value (string var) {
  /* FIXME: tp is wrong (and consequently, crashes TeXmacs)
   *   when we call this routine from inside the code which
   *   is triggered by a button, for example.
   *
   * Test: fire TeXmacs, then open a new Graphics, then click
   *   on the icon for going in spline mode. Then it crashes,
   *   because we call (get-env-tree) from inside the Scheme.
   *   If we call (get-env-tree-at ... (cDr (cursor-path))),
   *   then it works.
   */
  return get_env_value (var, tp);
}

bool
edit_typeset_rep::defined_at_init (string var) {
  if (init->contains (var)) return true;
  if (N (pre) == 0) typeset_preamble ();
  return pre->contains (var);
}

bool
edit_typeset_rep::defined_in_init (string var) {
  return init->contains (var);
}

tree
edit_typeset_rep::get_init_value (string var) {
  if (init->contains (var)) {
    tree t= init[var];
    if (var == BG_COLOR && is_func (t, PATTERN)) t= env->exec (t);
    return is_func (t, BACKUP, 2) ? t[0] : t;
  }
  if (N (pre) == 0) typeset_preamble ();
  tree t= pre[var];
  if (var == BG_COLOR && is_func (t, PATTERN)) t= env->exec (t);
  return is_func (t, BACKUP, 2) ? t[0] : t;
}

string
edit_typeset_rep::get_env_string (string var) {
  return as_string (get_env_value (var));
}

string
edit_typeset_rep::get_init_string (string var) {
  return as_string (get_init_value (var));
}

int
edit_typeset_rep::get_env_int (string var) {
  return as_int (get_env_value (var));
}

int
edit_typeset_rep::get_init_int (string var) {
  return as_int (get_init_value (var));
}

double
edit_typeset_rep::get_env_double (string var) {
  return as_double (get_env_value (var));
}

double
edit_typeset_rep::get_init_double (string var) {
  return as_double (get_init_value (var));
}

color
edit_typeset_rep::get_env_color (string var) {
  return named_color (as_string (get_env_value (var)));
}

color
edit_typeset_rep::get_init_color (string var) {
  return named_color (as_string (get_init_value (var)));
}

language
edit_typeset_rep::get_env_language () {
  string mode= get_env_string (MODE);
  if (mode == "text") return text_language (get_env_string (LANGUAGE));
  else if (mode == "math")
    return math_language (get_env_string (MATH_LANGUAGE));
  else return prog_language (get_env_string (PROG_LANGUAGE));
}

int
edit_typeset_rep::get_page_count () {
  return N (eb[0]);
}

SI
edit_typeset_rep::get_page_width (bool deco) {
  (void) get_env_string (PAGE_WIDTH);
  return (env->get_page_width (deco) + std_shrinkf - 1) / std_shrinkf;
}

SI
edit_typeset_rep::get_pages_width (bool deco) {
  (void) get_env_string (PAGE_WIDTH);
  return (env->get_pages_width (deco) + std_shrinkf - 1) / std_shrinkf;
}

SI
edit_typeset_rep::get_page_height (bool deco) {
  (void) get_env_string (PAGE_HEIGHT);
  return (env->get_page_height (deco) + std_shrinkf - 1) / std_shrinkf;
}

SI
edit_typeset_rep::get_total_width (bool deco) {
  SI w= eb->w ();
  if (!deco) {
    SI w1= env->get_pages_width (false);
    SI w2= env->get_pages_width (true);
    w-= (w2 - w1);
  }
  return (w + std_shrinkf - 1) / std_shrinkf;
}

SI
edit_typeset_rep::get_total_height (bool deco) {
  SI h= eb->h ();
  if (!deco) {
    SI  h1= env->get_page_height (false);
    SI  h2= env->get_page_height (true);
    int nr= get_page_count ();
    int nx= env->page_packet;
    int ny= ((nr + env->page_offset) + nx - 1) / nx;
    h-= ny * (h2 - h1);
  }
  return (h + std_shrinkf - 1) / std_shrinkf;
}

/******************************************************************************
 * Execution without typesetting
 ******************************************************************************/

static tree
simplify_execed (tree t) {
  if (is_atomic (t)) return t;
  int  i, n= N (t);
  tree r (t, n);
  for (i= 0; i < n; i++)
    r[i]= simplify_execed (t[i]);
  if (is_func (r, QUOTE, 1) && is_atomic (r[0])) return r[0];
  else return r;
}

static tree
expand_references (tree t, hashmap<string, tree> h) {
  if (is_atomic (t)) return t;
  if (is_func (t, REFERENCE, 1) || is_func (t, PAGEREF)) {
    string ref= as_string (simplify_execed (t[0]));
    if (h->contains (ref)) {
      int which= is_func (t, REFERENCE, 1) ? 0 : 1;
      return tree (HLINK, copy (h[ref][which]), "#" * ref);
    }
    return tree (HLINK, "?", "#" * ref);
  }
  int  i, n= N (t);
  tree r (t, n);
  for (i= 0; i < n; i++)
    r[i]= expand_references (t[i], h);
  return r;
}

static void
prefix_specific (hashmap<string, tree>& H, string prefix) {
  hashmap<string, tree> R;
  iterator<string>      it= iterate (H);
  while (it->busy ()) {
    string key= it->next ();
    string var= prefix * key;
    if (H->contains (var)) R (key)= H[var];
    else R (key)= H[key];
  }
  H= R;
}

tree
edit_typeset_rep::exec (tree t, hashmap<string, tree> H, bool expand_refs) {
  hashmap<string, tree> H2;
  env->read_env (H2);
  env->write_env (H);
  t= env->exec (t);
  if (expand_refs) t= expand_references (t, buf->data->ref);
  t= simplify_execed (t);
  t= simplify_correct (t);
  env->write_env (H2);
  return t;
}

tree
edit_typeset_rep::exec_texmacs (tree t, path p) {
  typeset_exec_until (p);
  return exec (t, cur[p]);
}

tree
edit_typeset_rep::exec_texmacs (tree t) {
  return exec_texmacs (t, rp * 0);
}

tree
edit_typeset_rep::exec_verbatim (tree t, path p) {
  t= convert_OTS1_symbols_to_universal_encoding (t);
  typeset_exec_until (p);
  hashmap<string, tree> H= copy (cur[p]);
  H ("TeXmacs")          = tree (MACRO, "TeXmacs");
  H ("LaTeX")            = tree (MACRO, "LaTeX");
  H ("TeX")              = tree (MACRO, "TeX");
  return exec (t, H);
}

tree
edit_typeset_rep::exec_verbatim (tree t) {
  return exec_verbatim (t, rp * 0);
}

static tree
search_doc_title (tree t) {
  if (is_atomic (t)) return "";
  else if (is_compound (t, "doc-title", 1)) return t[0];
  else {
    for (int i= 0; i < N (t); i++) {
      tree r= search_doc_title (t[i]);
      if (r != "") return r;
    }
    return "";
  }
}

tree
edit_typeset_rep::exec_html (tree t, path p) {
  t= convert_OTS1_symbols_to_universal_encoding (t);
  if (p == (rp * 0)) typeset_preamble ();
  typeset_exec_until (p);
  hashmap<string, tree> H= copy (cur[p]);
  tree patch             = as_tree (eval ("(stree->tree (tmhtml-env-patch))"));
  hashmap<string, tree> P= tree_hashmap (UNINIT, patch);
  H->join (P);
  prefix_specific (H, "tmhtml-");
  tree w (WITH);
  tree doc_title= search_doc_title (t);
  if (doc_title != "") w << tree ("html-doc-title") << doc_title;
  if (H->contains ("html-title")) w << tree ("html-title") << H["html-title"];
  if (H->contains ("html-css")) w << tree ("html-css") << H["html-css"];
  if (H->contains ("html-head-javascript"))
    w << tree ("html-head-javascript") << H["html-head-javascript"];
  if (H->contains ("html-head-javascript-src"))
    w << tree ("html-head-javascript-src") << H["html-head-javascript-src"];
  if (H->contains ("html-head-favicon"))
    w << tree ("html-head-favicon") << H["html-head-favicon"];
  if (H->contains ("html-extra-css"))
    w << tree ("html-extra-css") << H["html-extra-css"];
  if (H->contains ("html-extra-javascript-src"))
    w << tree ("html-extra-javascript-src") << H["html-extra-javascript-src"];
  if (H->contains ("html-extra-javascript"))
    w << tree ("html-extra-javascript") << H["html-extra-javascript"];
  if (H->contains ("html-site-version"))
    w << tree ("html-site-version") << H["html-site-version"];
  if (N (w) == 0) return exec (t, H);
  else {
    w << t;
    return exec (w, H);
  }
  // tree r= exec (t, H);
  // cout << "In: " << t << "\n";
  // cout << "Out: " << r << "\n";
  // return r;
}

tree
edit_typeset_rep::exec_html (tree t) {
  return exec_html (t, rp * 0);
}

static tree
value_to_compound (tree t, hashmap<string, tree> h) {
  if (is_atomic (t)) return t;
  else if (is_func (t, VALUE, 1) && is_atomic (t[0]) &&
           h->contains (t[0]->label))
    return compound (t[0]->label);
  else {
    int  i, n= N (t);
    tree r (t, n);
    for (i= 0; i < n; i++)
      r[i]= value_to_compound (t[i], h);
    return r;
  }
}

tree
edit_typeset_rep::exec_latex (tree t, path p) {
  t= convert_OTS1_symbols_to_universal_encoding (t);
  bool expand_unknown_macros=
      "on" ==
      as_string (call ("get-preference", "texmacs->latex:expand-macros"));
  bool expand_user_macro=
      "on" ==
      as_string (call ("get-preference", "texmacs->latex:expand-user-macros"));
  if (!expand_unknown_macros && !expand_user_macro) return t;
  if (p == (rp * 0)) typeset_preamble ();
  typeset_exec_until (p);
  hashmap<string, tree> H = copy (cur[p]);
  object                l = null_object ();
  iterator<string>      it= iterate (H);
  while (it->busy ())
    l= cons (object (it->next ()), l);
  tree patch= as_tree (call ("stree->tree", call ("tmtex-env-patch", t, l)));
  hashmap<string, tree> P= tree_hashmap (UNINIT, patch);
  H->join (P);
  prefix_specific (H, "tmlatex-");

  if (!expand_user_macro && is_document (t) &&
      is_compound (t[0], "hide-preamble")) {
    tree r= copy (t);
    r[0]  = "";
    r     = exec (value_to_compound (r, P), H, false);
    r[0]  = exec (t[0], H, false);
    return r;
  }
  else {
    tree r= exec (value_to_compound (t, P), H, false);
    return r;
  }
}

tree
edit_typeset_rep::exec_latex (tree t) {
  return exec_latex (t, rp * 0);
}

tree
edit_typeset_rep::texmacs_exec (tree t) {
  return ::texmacs_exec (env, t);
}

tree
edit_typeset_rep::var_texmacs_exec (tree t) {
  typeset_exec_until (tp);
  env->write_env (cur[tp]);
  env->update_frame ();
  return texmacs_exec (t);
}

/******************************************************************************
 * Wrappers for editing animations
 ******************************************************************************/

tree
edit_typeset_rep::checkout_animation (tree t) {
  path p= search_upwards (ANIM_STATIC);
  if (is_nil (p)) p= search_upwards (ANIM_DYNAMIC);
  if (is_nil (p)) p= search_upwards ("anim-edit");
  if (!is_nil (p)) {
    typeset_exec_until (p);
    env->write_env (cur[p]);
  }
  return env->checkout_animation (t);
}

tree
edit_typeset_rep::commit_animation (tree t) {
  path p= search_upwards ("anim-edit");
  if (is_nil (p)) p= search_upwards (ANIM_STATIC);
  if (is_nil (p)) p= search_upwards (ANIM_DYNAMIC);
  if (!is_nil (p)) {
    typeset_exec_until (p);
    env->write_env (cur[p]);
  }
  return env->commit_animation (t);
}

/******************************************************************************
 * Initialization
 ******************************************************************************/

void
edit_typeset_rep::change_style (tree t) {
  bool changed= (the_style != t);
  the_style   = copy (t);
  if (changed) {
    require_save ();
    notify_change (THE_ENVIRONMENT);
  }
}

void
edit_typeset_rep::init_style () {
  notify_change (THE_ENVIRONMENT);
}

void
edit_typeset_rep::init_style (string name) {
  if ((name == "none") || (name == "") || (name == "style")) the_style= TUPLE;
  else if (arity (the_style) == 0) the_style= tree (TUPLE, name);
  else the_style= tree (TUPLE, name) * the_style (1, N (the_style));
  require_save ();
  notify_change (THE_ENVIRONMENT);
}

tree
edit_typeset_rep::get_init_all () {
  return as_tree (init);
}

void
edit_typeset_rep::init_env (string var, tree by) {
  if (init (var) == by) return;
  init (var)= by;
  if (var == "full-screen-mode") return;
  if (var != PAGE_SCREEN_WIDTH && var != PAGE_SCREEN_HEIGHT &&
      var != ZOOM_FACTOR)
    require_save ();
  notify_change (THE_ENVIRONMENT);
}

void
edit_typeset_rep::init_default (string var) {
  if (!init->contains (var)) return;
  init->reset (var);
  if (stydef->contains (var)) pre (var)= stydef[var];
  else pre->reset (var);
  notify_change (THE_ENVIRONMENT);
}

/******************************************************************************
 * Actual typesetting
 ******************************************************************************/

void
edit_typeset_rep::typeset_sub (SI& x1, SI& y1, SI& x2, SI& y2) {
  // time_t t1= texmacs_time ();
  typeset_prepare ();
  eb= empty_box (reverse (rp));
  // saves memory, also necessary for change_log update
  bench_start ("typeset");
  try {
    eb= ::typeset (ttt, x1, y1, x2, y2);
  } catch (string msg) {
    the_exception= msg;
    std_error << "Typesetting failure, resetting to empty document\n";
    assign (rp, tree (DOCUMENT, ""));
    ::notify_assign (ttt, path (), subtree (et, rp));
    eb= ::typeset (ttt, x1, y1, x2, y2);
  }
  handle_exceptions ();
  if (DEBUG_BENCH) bench_end ("typeset");
  // time_t t2= texmacs_time ();
  // if (t2 - t1 >= 10) cout << "typeset took " << t2-t1 << "ms\n";
  picture_cache_clean ();
}

static void
report_missing (hashmap<string, tree> missing) {
  array<string> a;
  for (iterator<string> it= iterate (missing); it->busy (); a << it->next ()) {
  }
  merge_sort (a);
  for (int i= 0; i < N (a); i++)
    if (!starts (a[i], "bib-"))
      typeset_warning << "Undefined reference " << a[i] << LF;
}

static void
report_redefined (array<tree> redefined) {
  for (int i= 0; i < N (redefined); i++) {
    tree t= redefined[i];
    if (t[1]->label == "") typeset_warning << "Redefined " << t[0]->label << LF;
    else
      typeset_warning << "Redefined " << t[0]->label << " as " << t[1]->label
                      << LF;
  }
}

static void
clean_unused (hashmap<string, tree>& refs, hashmap<string, bool> used) {
  array<string> a;
  for (iterator<string> it= iterate (refs); it->busy ();) {
    string key= it->next ();
    if (!used->contains (key)) a << key;
  }
  for (int i= 0; i < N (a); i++)
    refs->reset (a[i]);
}

void
edit_typeset_rep::typeset (SI& x1, SI& y1, SI& x2, SI& y2) {
  int missing_nr  = INT_MAX;
  int redefined_nr= INT_MAX;
  x1              = MAX_SI;
  y1              = MAX_SI;
  x2              = MIN_SI;
  y2              = MIN_SI;
  while (true) {
    SI sx1, sy1, sx2, sy2;
    typeset_sub (sx1, sy1, sx2, sy2);
    x1= min (x1, sx1);
    y1= min (y1, sy1);
    x2= max (x2, sx2);
    y2= max (y2, sy2);
    if (!env->complete) break;
    env->complete= false;
    clean_unused (env->local_ref, env->touched);
    if (N (env->missing) == 0 && N (env->redefined) == 0) break;
    if ((N (env->missing) == missing_nr &&
         N (env->redefined) == redefined_nr) ||
        (N (env->missing) > missing_nr || N (env->redefined) > redefined_nr)) {
      report_missing (env->missing);
      report_redefined (env->redefined);
      break;
    }
    missing_nr  = N (env->missing);
    redefined_nr= N (env->redefined);
    ::notify_assign (ttt, path (), ttt->br->st);
  }
}

void
edit_typeset_rep::typeset_forced () {
  // cout << "Typeset forced\n";
  SI x1, y1, x2, y2;
  typeset (x1, y1, x2, y2);
}

void
edit_typeset_rep::typeset_invalidate (path p) {
  if (rp <= p) {
    // cout << "Invalidate " << p << "\n";
    notify_change (THE_TREE);
    ::notify_assign (ttt, p / rp, subtree (et, p));
  }
}

void
edit_typeset_rep::typeset_invalidate_all () {
  // cout << "Invalidate all\n";
  notify_change (THE_ENVIRONMENT);
  typeset_preamble ();
  ::notify_assign (ttt, path (), subtree (et, rp));
}

void
edit_typeset_rep::typeset_invalidate_players (path p, bool reattach) {
  if (rp <= p) {
    tree     t= subtree (et, p);
    blackbox bb;
    bool     ok= t->data->get_contents (ADDENDUM_PLAYER, bb);
    if (ok) {
      if (reattach) tree_addendum_delete (t, ADDENDUM_PLAYER);
      typeset_invalidate (p);
    }
    if (is_compound (t)) {
      int i, n= N (t);
      for (i= 0; i < n; i++)
        typeset_invalidate_players (p * i, reattach);
    }
  }
}
